Skip to content

组合继承(原型链+构造函数)(推荐 Es6,过时禁止使用的)

结合了原型链继承和借用构造函数继承的优点。思路就是使用原型链实现对原型属性和方法的继承,而通过借用构造函数来实现对实例属性的继承。

这样,既通过在原型上定义方法实现了函数复用,又能够保证每个实例都有它自己的属性。

  1. 组合继承

    • 组合继承是将原型链继承和构造函数继承结合起来使用,既通过原型链继承父类的原型方法,又通过构造函数继承父类的属性。
    javascript
    function Parent(name) {
      this.name = name;
    }
    Parent.prototype.sayHello = function () {
      console.log("Hello, I am " + this.name);
    };
    
    function Child(name) {
      Parent.call(this, name); // 构造函数继承
    }
    Child.prototype = new Parent(); // 原型链继承
    Child.prototype.constructor = Child; // 修正构造函数指向
    var child = new Child("Child");
    child.sayHello(); // 输出 "Hello, I am Child"

缺陷例子

  • 构造函数被调用两次:组合继承通过在子类构造函数中调用父类构造函数来实现属性的继承,同时又通过将子类原型指向一个父类实例来继承父类的方法。但是,在调用父类构造函数时,父类的属性会被子类实例化时的属性覆盖,导致构造函数被调用两次,一次是在子类构造函数中,另一次是在将子类原型指向父类实例时。

  • 原型链上存在冗余的属性:由于组合继承同时使用了原型链继承和构造函数继承,因此父类的属性会被复制到子类的原型上,导致原型链上存在冗余的属性,占用额外的内存空间。

  • 无法避免父类构造函数中的属性被重复初始化:在子类构造函数中调用父类构造函数时,父类构造函数内的属性会被初始化一次。但是,由于子类原型指向了父类的实例,父类构造函数内的属性又会被初始化一次,导致属性被重复初始化。

  • 不够简洁:组合继承需要在子类构造函数中调用父类构造函数,同时又需要将子类原型指向父类实例,这使得代码不够简洁,增加了阅读和维护的难度。

  • 性能问题:由于构造函数被调用两次以及原型链上存在冗余的属性,组合继承可能会导致性能问题,尤其是在创建大量子类实例时。

js
// 父类构造函数
function Parent(name) {
  this.name = name;
  this.colors = ["red", "blue", "green"];
}

// 父类原型方法
Parent.prototype.sayHello = function () {
  console.log("Hello, I'm " + this.name);
};

// 子类构造函数
function Child(name, age) {
  // 第一次调用父类构造函数
  Parent.call(this, name);
  this.age = age;
}

// 设置子类原型
Child.prototype = new Parent(); // 第二次调用父类构造函数
Child.prototype.constructor = Child; // 修正构造函数指向

// 创建子类实例
var child1 = new Child("Alice", 10);
var child2 = new Child("Bob", 12);

// 问题1:构造函数被调用两次

// 问题2:原型链上存在冗余的属性
console.log(child1.colors === child2.colors); // 输出: false

ES6 新增了一个方法,Object.setPrototypeOf,可以直接创建关联,而且不用手动添加 constructor 属性

js
// 继承
Object.setPrototypeOf(Child.prototype, Parent.prototype);

console.log(Child.prototype.constructor === Child); // true